/*
 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javax.swing.plaf.basic;

import sun.swing.DefaultLookup;
import sun.swing.UIAction;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.*;
import javax.swing.plaf.*;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;

import java.awt.Component;
import java.awt.Rectangle;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Insets;
import java.awt.Graphics;
import java.awt.event.*;

/**
 * A default L&amp;F implementation of ScrollPaneUI.
 *
 * @author Hans Muller
 */
public class BasicScrollPaneUI
    extends ScrollPaneUI implements ScrollPaneConstants {

  protected JScrollPane scrollpane;
  protected ChangeListener vsbChangeListener;
  protected ChangeListener hsbChangeListener;
  protected ChangeListener viewportChangeListener;
  protected PropertyChangeListener spPropertyChangeListener;
  private MouseWheelListener mouseScrollListener;
  private int oldExtent = Integer.MIN_VALUE;

  /**
   * PropertyChangeListener installed on the vertical scrollbar.
   */
  private PropertyChangeListener vsbPropertyChangeListener;

  /**
   * PropertyChangeListener installed on the horizontal scrollbar.
   */
  private PropertyChangeListener hsbPropertyChangeListener;

  private Handler handler;

  /**
   * State flag that shows whether setValue() was called from a user program
   * before the value of "extent" was set in right-to-left component
   * orientation.
   */
  private boolean setValueCalled = false;


  public static ComponentUI createUI(JComponent x) {
    return new BasicScrollPaneUI();
  }

  static void loadActionMap(LazyActionMap map) {
    map.put(new Actions(Actions.SCROLL_UP));
    map.put(new Actions(Actions.SCROLL_DOWN));
    map.put(new Actions(Actions.SCROLL_HOME));
    map.put(new Actions(Actions.SCROLL_END));
    map.put(new Actions(Actions.UNIT_SCROLL_UP));
    map.put(new Actions(Actions.UNIT_SCROLL_DOWN));
    map.put(new Actions(Actions.SCROLL_LEFT));
    map.put(new Actions(Actions.SCROLL_RIGHT));
    map.put(new Actions(Actions.UNIT_SCROLL_RIGHT));
    map.put(new Actions(Actions.UNIT_SCROLL_LEFT));
  }


  public void paint(Graphics g, JComponent c) {
    Border vpBorder = scrollpane.getViewportBorder();
    if (vpBorder != null) {
      Rectangle r = scrollpane.getViewportBorderBounds();
      vpBorder.paintBorder(scrollpane, g, r.x, r.y, r.width, r.height);
    }
  }


  /**
   * @return new Dimension(Short.MAX_VALUE, Short.MAX_VALUE)
   */
  public Dimension getMaximumSize(JComponent c) {
    return new Dimension(Short.MAX_VALUE, Short.MAX_VALUE);
  }


  protected void installDefaults(JScrollPane scrollpane) {
    LookAndFeel.installBorder(scrollpane, "ScrollPane.border");
    LookAndFeel.installColorsAndFont(scrollpane,
        "ScrollPane.background",
        "ScrollPane.foreground",
        "ScrollPane.font");

    Border vpBorder = scrollpane.getViewportBorder();
    if ((vpBorder == null) || (vpBorder instanceof UIResource)) {
      vpBorder = UIManager.getBorder("ScrollPane.viewportBorder");
      scrollpane.setViewportBorder(vpBorder);
    }
    LookAndFeel.installProperty(scrollpane, "opaque", Boolean.TRUE);
  }


  protected void installListeners(JScrollPane c) {
    vsbChangeListener = createVSBChangeListener();
    vsbPropertyChangeListener = createVSBPropertyChangeListener();
    hsbChangeListener = createHSBChangeListener();
    hsbPropertyChangeListener = createHSBPropertyChangeListener();
    viewportChangeListener = createViewportChangeListener();
    spPropertyChangeListener = createPropertyChangeListener();

    JViewport viewport = scrollpane.getViewport();
    JScrollBar vsb = scrollpane.getVerticalScrollBar();
    JScrollBar hsb = scrollpane.getHorizontalScrollBar();

    if (viewport != null) {
      viewport.addChangeListener(viewportChangeListener);
    }
    if (vsb != null) {
      vsb.getModel().addChangeListener(vsbChangeListener);
      vsb.addPropertyChangeListener(vsbPropertyChangeListener);
    }
    if (hsb != null) {
      hsb.getModel().addChangeListener(hsbChangeListener);
      hsb.addPropertyChangeListener(hsbPropertyChangeListener);
    }

    scrollpane.addPropertyChangeListener(spPropertyChangeListener);

    mouseScrollListener = createMouseWheelListener();
    scrollpane.addMouseWheelListener(mouseScrollListener);

  }

  protected void installKeyboardActions(JScrollPane c) {
    InputMap inputMap = getInputMap(JComponent.
        WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);

    SwingUtilities.replaceUIInputMap(c, JComponent.
        WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, inputMap);

    LazyActionMap.installLazyActionMap(c, BasicScrollPaneUI.class,
        "ScrollPane.actionMap");
  }

  InputMap getInputMap(int condition) {
    if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
      InputMap keyMap = (InputMap) DefaultLookup.get(scrollpane, this,
          "ScrollPane.ancestorInputMap");
      InputMap rtlKeyMap;

      if (scrollpane.getComponentOrientation().isLeftToRight() ||
          ((rtlKeyMap = (InputMap) DefaultLookup.get(scrollpane, this,
              "ScrollPane.ancestorInputMap.RightToLeft")) == null)) {
        return keyMap;
      } else {
        rtlKeyMap.setParent(keyMap);
        return rtlKeyMap;
      }
    }
    return null;
  }

  public void installUI(JComponent x) {
    scrollpane = (JScrollPane) x;
    installDefaults(scrollpane);
    installListeners(scrollpane);
    installKeyboardActions(scrollpane);
  }


  protected void uninstallDefaults(JScrollPane c) {
    LookAndFeel.uninstallBorder(scrollpane);

    if (scrollpane.getViewportBorder() instanceof UIResource) {
      scrollpane.setViewportBorder(null);
    }
  }


  protected void uninstallListeners(JComponent c) {
    JViewport viewport = scrollpane.getViewport();
    JScrollBar vsb = scrollpane.getVerticalScrollBar();
    JScrollBar hsb = scrollpane.getHorizontalScrollBar();

    if (viewport != null) {
      viewport.removeChangeListener(viewportChangeListener);
    }
    if (vsb != null) {
      vsb.getModel().removeChangeListener(vsbChangeListener);
      vsb.removePropertyChangeListener(vsbPropertyChangeListener);
    }
    if (hsb != null) {
      hsb.getModel().removeChangeListener(hsbChangeListener);
      hsb.removePropertyChangeListener(hsbPropertyChangeListener);
    }

    scrollpane.removePropertyChangeListener(spPropertyChangeListener);

    if (mouseScrollListener != null) {
      scrollpane.removeMouseWheelListener(mouseScrollListener);
    }

    vsbChangeListener = null;
    hsbChangeListener = null;
    viewportChangeListener = null;
    spPropertyChangeListener = null;
    mouseScrollListener = null;
    handler = null;
  }


  protected void uninstallKeyboardActions(JScrollPane c) {
    SwingUtilities.replaceUIActionMap(c, null);
    SwingUtilities.replaceUIInputMap(c, JComponent.
        WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
  }


  public void uninstallUI(JComponent c) {
    uninstallDefaults(scrollpane);
    uninstallListeners(scrollpane);
    uninstallKeyboardActions(scrollpane);
    scrollpane = null;
  }

  private Handler getHandler() {
    if (handler == null) {
      handler = new Handler();
    }
    return handler;
  }

  protected void syncScrollPaneWithViewport() {
    JViewport viewport = scrollpane.getViewport();
    JScrollBar vsb = scrollpane.getVerticalScrollBar();
    JScrollBar hsb = scrollpane.getHorizontalScrollBar();
    JViewport rowHead = scrollpane.getRowHeader();
    JViewport colHead = scrollpane.getColumnHeader();
    boolean ltr = scrollpane.getComponentOrientation().isLeftToRight();

    if (viewport != null) {
      Dimension extentSize = viewport.getExtentSize();
      Dimension viewSize = viewport.getViewSize();
      Point viewPosition = viewport.getViewPosition();

      if (vsb != null) {
        int extent = extentSize.height;
        int max = viewSize.height;
        int value = Math.max(0, Math.min(viewPosition.y, max - extent));
        vsb.setValues(value, extent, 0, max);
      }

      if (hsb != null) {
        int extent = extentSize.width;
        int max = viewSize.width;
        int value;

        if (ltr) {
          value = Math.max(0, Math.min(viewPosition.x, max - extent));
        } else {
          int currentValue = hsb.getValue();

                    /* Use a particular formula to calculate "value"
                     * until effective x coordinate is calculated.
                     */
          if (setValueCalled && ((max - currentValue) == viewPosition.x)) {
            value = Math.max(0, Math.min(max - extent, currentValue));
                        /* After "extent" is set, turn setValueCalled flag off.
                         */
            if (extent != 0) {
              setValueCalled = false;
            }
          } else {
            if (extent > max) {
              viewPosition.x = max - extent;
              viewport.setViewPosition(viewPosition);
              value = 0;
            } else {
                           /* The following line can't handle a small value of
                            * viewPosition.x like Integer.MIN_VALUE correctly
                            * because (max - extent - viewPositoiin.x) causes
                            * an overflow. As a result, value becomes zero.
                            * (e.g. setViewPosition(Integer.MAX_VALUE, ...)
                            *       in a user program causes a overflow.
                            *       Its expected value is (max - extent).)
                            * However, this seems a trivial bug and adding a
                            * fix makes this often-called method slow, so I'll
                            * leave it until someone claims.
                            */
              value = Math.max(0, Math.min(max - extent, max - extent - viewPosition.x));
              if (oldExtent > extent) {
                value -= oldExtent - extent;
              }
            }
          }
        }
        oldExtent = extent;
        hsb.setValues(value, extent, 0, max);
      }

      if (rowHead != null) {
        Point p = rowHead.getViewPosition();
        p.y = viewport.getViewPosition().y;
        p.x = 0;
        rowHead.setViewPosition(p);
      }

      if (colHead != null) {
        Point p = colHead.getViewPosition();
        if (ltr) {
          p.x = viewport.getViewPosition().x;
        } else {
          p.x = Math.max(0, viewport.getViewPosition().x);
        }
        p.y = 0;
        colHead.setViewPosition(p);
      }
    }
  }

  /**
   * Returns the baseline.
   *
   * @throws NullPointerException {@inheritDoc}
   * @throws IllegalArgumentException {@inheritDoc}
   * @see javax.swing.JComponent#getBaseline(int, int)
   * @since 1.6
   */
  public int getBaseline(JComponent c, int width, int height) {
    if (c == null) {
      throw new NullPointerException("Component must be non-null");
    }

    if (width < 0 || height < 0) {
      throw new IllegalArgumentException("Width and height must be >= 0");
    }

    JViewport viewport = scrollpane.getViewport();
    Insets spInsets = scrollpane.getInsets();
    int y = spInsets.top;
    height = height - spInsets.top - spInsets.bottom;
    width = width - spInsets.left - spInsets.right;
    JViewport columnHeader = scrollpane.getColumnHeader();
    if (columnHeader != null && columnHeader.isVisible()) {
      Component header = columnHeader.getView();
      if (header != null && header.isVisible()) {
        // Header is always given it's preferred size.
        Dimension headerPref = header.getPreferredSize();
        int baseline = header.getBaseline(headerPref.width,
            headerPref.height);
        if (baseline >= 0) {
          return y + baseline;
        }
      }
      Dimension columnPref = columnHeader.getPreferredSize();
      height -= columnPref.height;
      y += columnPref.height;
    }
    Component view = (viewport == null) ? null : viewport.getView();
    if (view != null && view.isVisible() &&
        view.getBaselineResizeBehavior() ==
            Component.BaselineResizeBehavior.CONSTANT_ASCENT) {
      Border viewportBorder = scrollpane.getViewportBorder();
      if (viewportBorder != null) {
        Insets vpbInsets = viewportBorder.getBorderInsets(scrollpane);
        y += vpbInsets.top;
        height = height - vpbInsets.top - vpbInsets.bottom;
        width = width - vpbInsets.left - vpbInsets.right;
      }
      if (view.getWidth() > 0 && view.getHeight() > 0) {
        Dimension min = view.getMinimumSize();
        width = Math.max(min.width, view.getWidth());
        height = Math.max(min.height, view.getHeight());
      }
      if (width > 0 && height > 0) {
        int baseline = view.getBaseline(width, height);
        if (baseline > 0) {
          return y + baseline;
        }
      }
    }
    return -1;
  }

  /**
   * Returns an enum indicating how the baseline of the component
   * changes as the size changes.
   *
   * @throws NullPointerException {@inheritDoc}
   * @see javax.swing.JComponent#getBaseline(int, int)
   * @since 1.6
   */
  public Component.BaselineResizeBehavior getBaselineResizeBehavior(
      JComponent c) {
    super.getBaselineResizeBehavior(c);
    // Baseline is either from the header, in which case it's always
    // the same size and therefor can be created as CONSTANT_ASCENT.
    // If the header doesn't have a baseline than the baseline will only
    // be valid if it's BaselineResizeBehavior is
    // CONSTANT_ASCENT, so, return CONSTANT_ASCENT.
    return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
  }


  /**
   * Listener for viewport events.
   */
  public class ViewportChangeHandler implements ChangeListener {

    // NOTE: This class exists only for backward compatibility. All
    // its functionality has been moved into Handler. If you need to add
    // new functionality add it to the Handler, but make sure this
    // class calls into the Handler.

    public void stateChanged(ChangeEvent e) {
      getHandler().stateChanged(e);
    }
  }

  protected ChangeListener createViewportChangeListener() {
    return getHandler();
  }


  /**
   * Horizontal scrollbar listener.
   */
  public class HSBChangeListener implements ChangeListener {

    // NOTE: This class exists only for backward compatibility. All
    // its functionality has been moved into Handler. If you need to add
    // new functionality add it to the Handler, but make sure this
    // class calls into the Handler.

    public void stateChanged(ChangeEvent e) {
      getHandler().stateChanged(e);
    }
  }

  /**
   * Returns a <code>PropertyChangeListener</code> that will be installed
   * on the horizontal <code>JScrollBar</code>.
   */
  private PropertyChangeListener createHSBPropertyChangeListener() {
    return getHandler();
  }

  protected ChangeListener createHSBChangeListener() {
    return getHandler();
  }


  /**
   * Vertical scrollbar listener.
   */
  public class VSBChangeListener implements ChangeListener {

    // NOTE: This class exists only for backward compatibility. All
    // its functionality has been moved into Handler. If you need to add
    // new functionality add it to the Handler, but make sure this
    // class calls into the Handler.

    public void stateChanged(ChangeEvent e) {
      getHandler().stateChanged(e);
    }
  }


  /**
   * Returns a <code>PropertyChangeListener</code> that will be installed
   * on the vertical <code>JScrollBar</code>.
   */
  private PropertyChangeListener createVSBPropertyChangeListener() {
    return getHandler();
  }

  protected ChangeListener createVSBChangeListener() {
    return getHandler();
  }

  /**
   * MouseWheelHandler is an inner class which implements the
   * MouseWheelListener interface.  MouseWheelHandler responds to
   * MouseWheelEvents by scrolling the JScrollPane appropriately.
   * If the scroll pane's
   * <code>isWheelScrollingEnabled</code>
   * method returns false, no scrolling occurs.
   *
   * @see javax.swing.JScrollPane#isWheelScrollingEnabled
   * @see #createMouseWheelListener
   * @see java.awt.event.MouseWheelListener
   * @see java.awt.event.MouseWheelEvent
   * @since 1.4
   */
  protected class MouseWheelHandler implements MouseWheelListener {

    // NOTE: This class exists only for backward compatibility. All
    // its functionality has been moved into Handler. If you need to add
    // new functionality add it to the Handler, but make sure this
    // class calls into the Handler.

    /**
     * Called when the mouse wheel is rotated while over a
     * JScrollPane.
     *
     * @param e MouseWheelEvent to be handled
     * @since 1.4
     */
    public void mouseWheelMoved(MouseWheelEvent e) {
      getHandler().mouseWheelMoved(e);
    }
  }

  /**
   * Creates an instance of MouseWheelListener, which is added to the
   * JScrollPane by installUI().  The returned MouseWheelListener is used
   * to handle mouse wheel-driven scrolling.
   *
   * @return MouseWheelListener which implements wheel-driven scrolling
   * @see #installUI
   * @see MouseWheelHandler
   * @since 1.4
   */
  protected MouseWheelListener createMouseWheelListener() {
    return getHandler();
  }

  protected void updateScrollBarDisplayPolicy(PropertyChangeEvent e) {
    scrollpane.revalidate();
    scrollpane.repaint();
  }


  protected void updateViewport(PropertyChangeEvent e) {
    JViewport oldViewport = (JViewport) (e.getOldValue());
    JViewport newViewport = (JViewport) (e.getNewValue());

    if (oldViewport != null) {
      oldViewport.removeChangeListener(viewportChangeListener);
    }

    if (newViewport != null) {
      Point p = newViewport.getViewPosition();
      if (scrollpane.getComponentOrientation().isLeftToRight()) {
        p.x = Math.max(p.x, 0);
      } else {
        int max = newViewport.getViewSize().width;
        int extent = newViewport.getExtentSize().width;
        if (extent > max) {
          p.x = max - extent;
        } else {
          p.x = Math.max(0, Math.min(max - extent, p.x));
        }
      }
      p.y = Math.max(p.y, 0);
      newViewport.setViewPosition(p);
      newViewport.addChangeListener(viewportChangeListener);
    }
  }


  protected void updateRowHeader(PropertyChangeEvent e) {
    JViewport newRowHead = (JViewport) (e.getNewValue());
    if (newRowHead != null) {
      JViewport viewport = scrollpane.getViewport();
      Point p = newRowHead.getViewPosition();
      p.y = (viewport != null) ? viewport.getViewPosition().y : 0;
      newRowHead.setViewPosition(p);
    }
  }


  protected void updateColumnHeader(PropertyChangeEvent e) {
    JViewport newColHead = (JViewport) (e.getNewValue());
    if (newColHead != null) {
      JViewport viewport = scrollpane.getViewport();
      Point p = newColHead.getViewPosition();
      if (viewport == null) {
        p.x = 0;
      } else {
        if (scrollpane.getComponentOrientation().isLeftToRight()) {
          p.x = viewport.getViewPosition().x;
        } else {
          p.x = Math.max(0, viewport.getViewPosition().x);
        }
      }
      newColHead.setViewPosition(p);
      scrollpane.add(newColHead, COLUMN_HEADER);
    }
  }

  private void updateHorizontalScrollBar(PropertyChangeEvent pce) {
    updateScrollBar(pce, hsbChangeListener, hsbPropertyChangeListener);
  }

  private void updateVerticalScrollBar(PropertyChangeEvent pce) {
    updateScrollBar(pce, vsbChangeListener, vsbPropertyChangeListener);
  }

  private void updateScrollBar(PropertyChangeEvent pce, ChangeListener cl,
      PropertyChangeListener pcl) {
    JScrollBar sb = (JScrollBar) pce.getOldValue();
    if (sb != null) {
      if (cl != null) {
        sb.getModel().removeChangeListener(cl);
      }
      if (pcl != null) {
        sb.removePropertyChangeListener(pcl);
      }
    }
    sb = (JScrollBar) pce.getNewValue();
    if (sb != null) {
      if (cl != null) {
        sb.getModel().addChangeListener(cl);
      }
      if (pcl != null) {
        sb.addPropertyChangeListener(pcl);
      }
    }
  }

  public class PropertyChangeHandler implements PropertyChangeListener {

    // NOTE: This class exists only for backward compatibility. All
    // its functionality has been moved into Handler. If you need to add
    // new functionality add it to the Handler, but make sure this
    // class calls into the Handler.

    public void propertyChange(PropertyChangeEvent e) {
      getHandler().propertyChange(e);
    }
  }


  /**
   * Creates an instance of PropertyChangeListener that's added to
   * the JScrollPane by installUI().  Subclasses can override this method
   * to return a custom PropertyChangeListener, e.g.
   * <pre>
   * class MyScrollPaneUI extends BasicScrollPaneUI {
   *    protected PropertyChangeListener <b>createPropertyChangeListener</b>() {
   *        return new MyPropertyChangeListener();
   *    }
   *    public class MyPropertyChangeListener extends PropertyChangeListener {
   *        public void propertyChange(PropertyChangeEvent e) {
   *            if (e.getPropertyName().equals("viewport")) {
   *                // do some extra work when the viewport changes
   *            }
   *            super.propertyChange(e);
   *        }
   *    }
   * }
   * </pre>
   *
   * @see java.beans.PropertyChangeListener
   * @see #installUI
   */
  protected PropertyChangeListener createPropertyChangeListener() {
    return getHandler();
  }


  private static class Actions extends UIAction {

    private static final String SCROLL_UP = "scrollUp";
    private static final String SCROLL_DOWN = "scrollDown";
    private static final String SCROLL_HOME = "scrollHome";
    private static final String SCROLL_END = "scrollEnd";
    private static final String UNIT_SCROLL_UP = "unitScrollUp";
    private static final String UNIT_SCROLL_DOWN = "unitScrollDown";
    private static final String SCROLL_LEFT = "scrollLeft";
    private static final String SCROLL_RIGHT = "scrollRight";
    private static final String UNIT_SCROLL_LEFT = "unitScrollLeft";
    private static final String UNIT_SCROLL_RIGHT = "unitScrollRight";


    Actions(String key) {
      super(key);
    }

    public void actionPerformed(ActionEvent e) {
      JScrollPane scrollPane = (JScrollPane) e.getSource();
      boolean ltr = scrollPane.getComponentOrientation().isLeftToRight();
      String key = getName();

      if (key == SCROLL_UP) {
        scroll(scrollPane, SwingConstants.VERTICAL, -1, true);
      } else if (key == SCROLL_DOWN) {
        scroll(scrollPane, SwingConstants.VERTICAL, 1, true);
      } else if (key == SCROLL_HOME) {
        scrollHome(scrollPane);
      } else if (key == SCROLL_END) {
        scrollEnd(scrollPane);
      } else if (key == UNIT_SCROLL_UP) {
        scroll(scrollPane, SwingConstants.VERTICAL, -1, false);
      } else if (key == UNIT_SCROLL_DOWN) {
        scroll(scrollPane, SwingConstants.VERTICAL, 1, false);
      } else if (key == SCROLL_LEFT) {
        scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? -1 : 1,
            true);
      } else if (key == SCROLL_RIGHT) {
        scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? 1 : -1,
            true);
      } else if (key == UNIT_SCROLL_LEFT) {
        scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? -1 : 1,
            false);
      } else if (key == UNIT_SCROLL_RIGHT) {
        scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? 1 : -1,
            false);
      }
    }

    private void scrollEnd(JScrollPane scrollpane) {
      JViewport vp = scrollpane.getViewport();
      Component view;
      if (vp != null && (view = vp.getView()) != null) {
        Rectangle visRect = vp.getViewRect();
        Rectangle bounds = view.getBounds();
        if (scrollpane.getComponentOrientation().isLeftToRight()) {
          vp.setViewPosition(new Point(bounds.width - visRect.width,
              bounds.height - visRect.height));
        } else {
          vp.setViewPosition(new Point(0,
              bounds.height - visRect.height));
        }
      }
    }

    private void scrollHome(JScrollPane scrollpane) {
      JViewport vp = scrollpane.getViewport();
      Component view;
      if (vp != null && (view = vp.getView()) != null) {
        if (scrollpane.getComponentOrientation().isLeftToRight()) {
          vp.setViewPosition(new Point(0, 0));
        } else {
          Rectangle visRect = vp.getViewRect();
          Rectangle bounds = view.getBounds();
          vp.setViewPosition(new Point(bounds.width - visRect.width, 0));
        }
      }
    }

    private void scroll(JScrollPane scrollpane, int orientation,
        int direction, boolean block) {
      JViewport vp = scrollpane.getViewport();
      Component view;
      if (vp != null && (view = vp.getView()) != null) {
        Rectangle visRect = vp.getViewRect();
        Dimension vSize = view.getSize();
        int amount;

        if (view instanceof Scrollable) {
          if (block) {
            amount = ((Scrollable) view).getScrollableBlockIncrement
                (visRect, orientation, direction);
          } else {
            amount = ((Scrollable) view).getScrollableUnitIncrement
                (visRect, orientation, direction);
          }
        } else {
          if (block) {
            if (orientation == SwingConstants.VERTICAL) {
              amount = visRect.height;
            } else {
              amount = visRect.width;
            }
          } else {
            amount = 10;
          }
        }
        if (orientation == SwingConstants.VERTICAL) {
          visRect.y += (amount * direction);
          if ((visRect.y + visRect.height) > vSize.height) {
            visRect.y = Math.max(0, vSize.height - visRect.height);
          } else if (visRect.y < 0) {
            visRect.y = 0;
          }
        } else {
          if (scrollpane.getComponentOrientation().isLeftToRight()) {
            visRect.x += (amount * direction);
            if ((visRect.x + visRect.width) > vSize.width) {
              visRect.x = Math.max(0, vSize.width - visRect.width);
            } else if (visRect.x < 0) {
              visRect.x = 0;
            }
          } else {
            visRect.x -= (amount * direction);
            if (visRect.width > vSize.width) {
              visRect.x = vSize.width - visRect.width;
            } else {
              visRect.x = Math.max(0, Math.min(vSize.width - visRect.width, visRect.x));
            }
          }
        }
        vp.setViewPosition(visRect.getLocation());
      }
    }
  }


  class Handler implements ChangeListener, PropertyChangeListener, MouseWheelListener {

    //
    // MouseWheelListener
    //
    public void mouseWheelMoved(MouseWheelEvent e) {
      if (scrollpane.isWheelScrollingEnabled() &&
          e.getWheelRotation() != 0) {
        JScrollBar toScroll = scrollpane.getVerticalScrollBar();
        int direction = e.getWheelRotation() < 0 ? -1 : 1;
        int orientation = SwingConstants.VERTICAL;

        // find which scrollbar to scroll, or return if none
        if (toScroll == null || !toScroll.isVisible()) {
          toScroll = scrollpane.getHorizontalScrollBar();
          if (toScroll == null || !toScroll.isVisible()) {
            return;
          }
          orientation = SwingConstants.HORIZONTAL;
        } else if (e.isShiftDown()) {
          JScrollBar hScroll = scrollpane.getHorizontalScrollBar();
          if (hScroll != null && hScroll.isVisible()) {
            toScroll = hScroll;
            orientation = SwingConstants.HORIZONTAL;
          }
        }

        e.consume();

        if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) {
          JViewport vp = scrollpane.getViewport();
          if (vp == null) {
            return;
          }
          Component comp = vp.getView();
          int units = Math.abs(e.getUnitsToScroll());

          // When the scrolling speed is set to maximum, it's possible
          // for a single wheel click to scroll by more units than
          // will fit in the visible area.  This makes it
          // hard/impossible to get to certain parts of the scrolling
          // Component with the wheel.  To make for more accurate
          // low-speed scrolling, we limit scrolling to the block
          // increment if the wheel was only rotated one click.
          boolean limitScroll = Math.abs(e.getWheelRotation()) == 1;

          // Check if we should use the visibleRect trick
          Object fastWheelScroll = toScroll.getClientProperty(
              "JScrollBar.fastWheelScrolling");
          if (Boolean.TRUE == fastWheelScroll &&
              comp instanceof Scrollable) {
            // 5078454: Under maximum acceleration, we may scroll
            // by many 100s of units in ~1 second.
            //
            // BasicScrollBarUI.scrollByUnits() can bog down the EDT
            // with repaints in this situation.  However, the
            // Scrollable interface allows us to pass in an
            // arbitrary visibleRect.  This allows us to accurately
            // calculate the total scroll amount, and then update
            // the GUI once.  This technique provides much faster
            // accelerated wheel scrolling.
            Scrollable scrollComp = (Scrollable) comp;
            Rectangle viewRect = vp.getViewRect();
            int startingX = viewRect.x;
            boolean leftToRight =
                comp.getComponentOrientation().isLeftToRight();
            int scrollMin = toScroll.getMinimum();
            int scrollMax = toScroll.getMaximum() -
                toScroll.getModel().getExtent();

            if (limitScroll) {
              int blockIncr =
                  scrollComp.getScrollableBlockIncrement(viewRect,
                      orientation,
                      direction);
              if (direction < 0) {
                scrollMin = Math.max(scrollMin,
                    toScroll.getValue() - blockIncr);
              } else {
                scrollMax = Math.min(scrollMax,
                    toScroll.getValue() + blockIncr);
              }
            }

            for (int i = 0; i < units; i++) {
              int unitIncr =
                  scrollComp.getScrollableUnitIncrement(viewRect,
                      orientation, direction);
              // Modify the visible rect for the next unit, and
              // check to see if we're at the end already.
              if (orientation == SwingConstants.VERTICAL) {
                if (direction < 0) {
                  viewRect.y -= unitIncr;
                  if (viewRect.y <= scrollMin) {
                    viewRect.y = scrollMin;
                    break;
                  }
                } else { // (direction > 0
                  viewRect.y += unitIncr;
                  if (viewRect.y >= scrollMax) {
                    viewRect.y = scrollMax;
                    break;
                  }
                }
              } else {
                // Scroll left
                if ((leftToRight && direction < 0) ||
                    (!leftToRight && direction > 0)) {
                  viewRect.x -= unitIncr;
                  if (leftToRight) {
                    if (viewRect.x < scrollMin) {
                      viewRect.x = scrollMin;
                      break;
                    }
                  }
                }
                // Scroll right
                else if ((leftToRight && direction > 0) ||
                    (!leftToRight && direction < 0)) {
                  viewRect.x += unitIncr;
                  if (leftToRight) {
                    if (viewRect.x > scrollMax) {
                      viewRect.x = scrollMax;
                      break;
                    }
                  }
                } else {
                  assert false : "Non-sensical ComponentOrientation / scroll direction";
                }
              }
            }
            // Set the final view position on the ScrollBar
            if (orientation == SwingConstants.VERTICAL) {
              toScroll.setValue(viewRect.y);
            } else {
              if (leftToRight) {
                toScroll.setValue(viewRect.x);
              } else {
                // rightToLeft scrollbars are oriented with
                // minValue on the right and maxValue on the
                // left.
                int newPos = toScroll.getValue() -
                    (viewRect.x - startingX);
                if (newPos < scrollMin) {
                  newPos = scrollMin;
                } else if (newPos > scrollMax) {
                  newPos = scrollMax;
                }
                toScroll.setValue(newPos);
              }
            }
          } else {
            // Viewport's view is not a Scrollable, or fast wheel
            // scrolling is not enabled.
            BasicScrollBarUI.scrollByUnits(toScroll, direction,
                units, limitScroll);
          }
        } else if (e.getScrollType() ==
            MouseWheelEvent.WHEEL_BLOCK_SCROLL) {
          BasicScrollBarUI.scrollByBlock(toScroll, direction);
        }
      }
    }

    //
    // ChangeListener: This is added to the vieport, and hsb/vsb models.
    //
    public void stateChanged(ChangeEvent e) {
      JViewport viewport = scrollpane.getViewport();

      if (viewport != null) {
        if (e.getSource() == viewport) {
          syncScrollPaneWithViewport();
        } else {
          JScrollBar hsb = scrollpane.getHorizontalScrollBar();
          if (hsb != null && e.getSource() == hsb.getModel()) {
            hsbStateChanged(viewport, e);
          } else {
            JScrollBar vsb = scrollpane.getVerticalScrollBar();
            if (vsb != null && e.getSource() == vsb.getModel()) {
              vsbStateChanged(viewport, e);
            }
          }
        }
      }
    }

    private void vsbStateChanged(JViewport viewport, ChangeEvent e) {
      BoundedRangeModel model = (BoundedRangeModel) (e.getSource());
      Point p = viewport.getViewPosition();
      p.y = model.getValue();
      viewport.setViewPosition(p);
    }

    private void hsbStateChanged(JViewport viewport, ChangeEvent e) {
      BoundedRangeModel model = (BoundedRangeModel) (e.getSource());
      Point p = viewport.getViewPosition();
      int value = model.getValue();
      if (scrollpane.getComponentOrientation().isLeftToRight()) {
        p.x = value;
      } else {
        int max = viewport.getViewSize().width;
        int extent = viewport.getExtentSize().width;
        int oldX = p.x;

                /* Set new X coordinate based on "value".
                 */
        p.x = max - extent - value;

                /* If setValue() was called before "extent" was fixed,
                 * turn setValueCalled flag on.
                 */
        if ((extent == 0) && (value != 0) && (oldX == max)) {
          setValueCalled = true;
        } else {
                    /* When a pane without a horizontal scroll bar was
                     * reduced and the bar appeared, the viewport should
                     * show the right side of the view.
                     */
          if ((extent != 0) && (oldX < 0) && (p.x == 0)) {
            p.x += value;
          }
        }
      }
      viewport.setViewPosition(p);
    }

    //
    // PropertyChangeListener: This is installed on both the JScrollPane
    // and the horizontal/vertical scrollbars.
    //

    // Listens for changes in the model property and reinstalls the
    // horizontal/vertical PropertyChangeListeners.
    public void propertyChange(PropertyChangeEvent e) {
      if (e.getSource() == scrollpane) {
        scrollPanePropertyChange(e);
      } else {
        sbPropertyChange(e);
      }
    }

    private void scrollPanePropertyChange(PropertyChangeEvent e) {
      String propertyName = e.getPropertyName();

      if (propertyName == "verticalScrollBarDisplayPolicy") {
        updateScrollBarDisplayPolicy(e);
      } else if (propertyName == "horizontalScrollBarDisplayPolicy") {
        updateScrollBarDisplayPolicy(e);
      } else if (propertyName == "viewport") {
        updateViewport(e);
      } else if (propertyName == "rowHeader") {
        updateRowHeader(e);
      } else if (propertyName == "columnHeader") {
        updateColumnHeader(e);
      } else if (propertyName == "verticalScrollBar") {
        updateVerticalScrollBar(e);
      } else if (propertyName == "horizontalScrollBar") {
        updateHorizontalScrollBar(e);
      } else if (propertyName == "componentOrientation") {
        scrollpane.revalidate();
        scrollpane.repaint();
      }
    }

    // PropertyChangeListener for the horizontal and vertical scrollbars.
    private void sbPropertyChange(PropertyChangeEvent e) {
      String propertyName = e.getPropertyName();
      Object source = e.getSource();

      if ("model" == propertyName) {
        JScrollBar sb = scrollpane.getVerticalScrollBar();
        BoundedRangeModel oldModel = (BoundedRangeModel) e.
            getOldValue();
        ChangeListener cl = null;

        if (source == sb) {
          cl = vsbChangeListener;
        } else if (source == scrollpane.getHorizontalScrollBar()) {
          sb = scrollpane.getHorizontalScrollBar();
          cl = hsbChangeListener;
        }
        if (cl != null) {
          if (oldModel != null) {
            oldModel.removeChangeListener(cl);
          }
          if (sb.getModel() != null) {
            sb.getModel().addChangeListener(cl);
          }
        }
      } else if ("componentOrientation" == propertyName) {
        if (source == scrollpane.getHorizontalScrollBar()) {
          JScrollBar hsb = scrollpane.getHorizontalScrollBar();
          JViewport viewport = scrollpane.getViewport();
          Point p = viewport.getViewPosition();
          if (scrollpane.getComponentOrientation().isLeftToRight()) {
            p.x = hsb.getValue();
          } else {
            p.x = viewport.getViewSize().width - viewport.getExtentSize().width - hsb.getValue();
          }
          viewport.setViewPosition(p);
        }
      }
    }
  }
}
